/*****************************************************
*
*               COPYRIGHT (c) 2015-2017 Broadlink Corporation
*                         All Rights Reserved
*
* The source code contained or described herein and all documents
* related to the source code ("Material") are owned by Broadlink
* Corporation or its licensors.  Title to the Material remains
* with Broadlink Corporation or its suppliers and licensors.
*
* The Material is protected by worldwide copyright and trade secret
* laws and treaty provisions. No part of the Material may be used,
* copied, reproduced, modified, published, uploaded, posted, transmitted,
* distributed, or disclosed in any way except in accordance with the
* applicable license agreement.
*
* No license under any patent, copyright, trade secret or other
* intellectual property right is granted to or conferred upon you by
* disclosure or delivery of the Materials, either expressly, by
* implication, inducement, estoppel, except in accordance with the
* applicable license agreement.
*
* Unless otherwise agreed by Broadlink in writing, you may not remove or
* alter this notice or any other notice embedded in Materials by Broadlink
* or Broadlink's suppliers or licensors in any way.
*
** Author: shengda.huang
** Date: 2018-09-19
*
*****************************************************/

/***** Include files ********************************/
#ifdef OS_LINUX
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#elif defined(OS_DNA)
#include "dna_sys_com.h"
#include "dna_utils.h"
#include "dna_errno.h"
#include "dna_common.h"
#include "dna_libc.h"
#include "dna_os.h"
#include "dna_sys_tools.h"
#endif

#include "syscom.h"
#include "mplink.h"
#include "bipbuffer.h"

/***** Defines **************************************/

#ifdef OS_LINUX
#define PRINTF  printf
#define MPLINK_LOCK(a)      pthread_spin_lock(a)
#define MPLINK_UNLOCK(a)    pthread_spin_unlock(a)
#define BIPBUF_LOCK(a)      pthread_spin_lock(a)
#define BIPBUF_UNLOCK(a)    pthread_spin_unlock(a)
#define SYSCOM_TRG(a, b)    do { \
                                pthread_mutex_lock(b); \
                                pthread_cond_signal(a); \
                                pthread_mutex_unlock(b); \
                            } while (0)
#define SYSCOM_WAIT(a, b)   do { \
                                pthread_mutex_lock(b); \
                                pthread_cond_wait(a, b); \
                                pthread_mutex_unlock(b); \
                            } while (0)
#elif defined(OS_DNA)
#define PRINTF  dna_printf
#define BIPBUF_LOCK(a)      dna_os_suspendall()
#define BIPBUF_UNLOCK(a)    dna_os_resumeall()
#define SYSCOM_QUEUE_DEEP_MAX       16
#endif

#ifdef SYSCOM_DBG_LOG_ENABLE
#define syscom_log_dbg(_fmt_, ...) \
            PRINTF("[SC][%s:%d][%s] "_fmt_"\r\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define syscom_printf       PRINTF
#else
#define syscom_log_dbg(_fmt_, ...)
#define syscom_printf
#endif

#define syscom_log_inf(_fmt_, ...) \
            PRINTF("[SC][%s:%d][%s] "_fmt_"\r\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define syscom_log_wrn(_fmt_, ...) \
            PRINTF("[SC][WRN][%s:%d][%s] "_fmt_"\r\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define syscom_log_err(_fmt_, ...) \
            PRINTF("[SC][ERR][%s:%d][%s] "_fmt_"\r\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__)

#define SYSCOM_MAGIC    0xdeadc0de

/***** Define structure *****************************/
struct syscom_mgr {
    short scid;
    void (*handler)(unsigned char *pbuf, unsigned int len);
};

struct syscom_queue {
    unsigned int magic;
    unsigned short scid;
    unsigned short len;
    unsigned char payload[0];
};

struct syscom_env {
    unsigned int    task_alive:1;
#ifdef OS_LINUX
    pthread_spinlock_t mplink_mgr_lock;
    pthread_spinlock_t bipbuf_lock;
    pthread_mutex_t    mtx;
    pthread_cond_t     cnd;
    pthread_t          task;
#elif defined(OS_DNA)
    // dna_mutex_handle_t  bip_mutex;
    dna_queue_handle_t  hdl_queue;
    dna_task_handle_t   hdl_task;
#endif

    struct mplink *mplink_mgr_free;
    struct mplink *mplink_mgr_using;
    struct mplink mplink_mgr[SYSCOM_HANDLER_NUM_MAX];
    struct syscom_mgr mgr[SYSCOM_HANDLER_NUM_MAX];

    struct bipbuf *bipbuff;
};

/***** Define constant ******************************/

/***** Define global ********************************/
static struct mplink *mplink_env_free = NULL;
static struct mplink *mplink_env_using = NULL;
static struct mplink mplink_env[SYSCON_ENV_NUM_MAX];
static struct syscom_env sc_env[SYSCON_ENV_NUM_MAX];
static pthread_spinlock_t mplink_env_lock;

/***** Define Prototype of functions ****************/

int syscom_send(syscom_handle_t syscom, unsigned short syscom_id, unsigned char *pbuf, int len)
{
    struct syscom_env *sc = (struct syscom_env *)syscom;
    struct syscom_queue eu;

    // should check whether task service start yet
    if (sc->task_alive == 0)
        return -1;

    eu.magic = SYSCOM_MAGIC;
    eu.scid = syscom_id;
    eu.len = len;

    BIPBUF_LOCK(&sc->bipbuf_lock);

    int totalsize = sizeof(struct syscom_queue) + len;
    char *buff = bipbuffer_reserve(sc->bipbuff, totalsize);
    if (buff == NULL) {
        BIPBUF_UNLOCK(&sc->bipbuf_lock);
        syscom_log_err("bipbuff reserve %d failed", totalsize);
        return -2;
    }

    memcpy(buff, &eu, sizeof(struct syscom_queue));
    memcpy(buff + sizeof(struct syscom_queue), pbuf, len);

    bipbuffer_commit(sc->bipbuff, totalsize);

    BIPBUF_UNLOCK(&sc->bipbuf_lock);

    SYSCOM_TRG(&sc->cnd, &sc->mtx);

    syscom_log_dbg("syscom send id %d payload size %u", eu.scid, eu.len);
    return 0;
}

static int find_mgr_by_scid(struct syscom_mgr *mgr, void *argv[], int argc)
{
    unsigned short scid = *(unsigned short *)argv[0];
    if (mgr->scid == scid)
        return !0;

    return 0;
}

static void *syscom_handler(void *arg)
{
    struct syscom_env *sc = (struct syscom_env *)arg;
    struct syscom_queue *eu;
    struct syscom_mgr *mgr;
    int pkgsize;
    unsigned int buffsize;
    void *argv[1];

    sc->task_alive = !0;
    syscom_log_inf("syscom handler daemon started");

    while (sc->task_alive) {
        SYSCOM_WAIT(&sc->cnd, &sc->mtx);

        while (1) {
            eu = (struct syscom_queue *)bipbuffer_get(sc->bipbuff, &buffsize);
            if (eu == NULL || buffsize == 0) {
                // syscom_log_err("eu %p buffsize %d", eu, buffsize);
                break ;
            }

            if (eu->magic != SYSCOM_MAGIC) {
                syscom_log_err("eu magic 0x%x err", eu->magic);

                BIPBUF_LOCK(&sc->bipbuf_lock);
                bipbuffer_decommit(sc->bipbuff, buffsize);
                BIPBUF_UNLOCK(&sc->bipbuf_lock);
                break ;
            }

            pkgsize = sizeof(struct syscom_queue) + eu->len;
            if (buffsize < pkgsize) {
                syscom_log_err("buffsize %d err should be %d", buffsize, pkgsize);

                BIPBUF_LOCK(&sc->bipbuf_lock);
                bipbuffer_decommit(sc->bipbuff, buffsize);
                BIPBUF_UNLOCK(&sc->bipbuf_lock);
                break ;
            }

            argv[0] = (void *)&eu->scid;
            mgr = mplink_iterator(sc->mplink_mgr_using, argv, 1, find_mgr_by_scid);
            if (mgr == NULL) {
                syscom_log_err("not find scid %d", eu->scid);

                BIPBUF_LOCK(&sc->bipbuf_lock);
                bipbuffer_decommit(sc->bipbuff, pkgsize);
                BIPBUF_UNLOCK(&sc->bipbuf_lock);
                continue ;
            }

            if (mgr->handler)
                (*mgr->handler)(eu->payload, eu->len);

            BIPBUF_LOCK(&sc->bipbuf_lock);
            bipbuffer_decommit(sc->bipbuff, pkgsize);
            BIPBUF_UNLOCK(&sc->bipbuf_lock);
        }
    }

    return NULL;
}

int syscom_register(syscom_handle_t syscom, void (*handler)(unsigned char *pbuf, unsigned int len))
{
    struct syscom_env *sc = (struct syscom_env *)syscom;
    struct syscom_mgr *mgr;

    MPLINK_LOCK(&sc->mplink_mgr_lock);
    mgr = mplink_new(&sc->mplink_mgr_free, &sc->mplink_mgr_using);
    MPLINK_UNLOCK(&sc->mplink_mgr_lock);
    if (mgr == NULL) {
        syscom_log_wrn("syscom register failed");
        return -1;
    }

    mgr->handler = handler;
    return (int)mgr->scid;
}

syscom_handle_t syscom_open(int stack_size, unsigned char *buff, int size)
{
    MPLINK_LOCK(&mplink_env_lock);
    struct syscom_env *sc = mplink_new(&mplink_env_free, &mplink_env_using);
    MPLINK_UNLOCK(&mplink_env_lock);
    if (sc == NULL) {
        syscom_log_err("new syscom env failed");
        return NULL;
    }

    int i, ret;
    for (i = 0; i < SYSCOM_HANDLER_NUM_MAX; ++i) {
        sc->mgr[i].scid = i + 1;
        sc->mgr[i].handler = NULL;
    }
    mplink_init(sc->mplink_mgr, SYSCOM_HANDLER_NUM_MAX, \
                &sc->mplink_mgr_free, &sc->mplink_mgr_using, \
                sc->mgr, sizeof(struct syscom_mgr));

    sc->bipbuff = (struct bipbuf *)buff;
    if (bipbuffer_construct(sc->bipbuff, size) != 0) {
        syscom_log_err("bipbuffer construct failed");
        return NULL;
    }

#ifdef OS_LINUX
    pthread_spin_init(&sc->mplink_mgr_lock, PTHREAD_PROCESS_PRIVATE);
    pthread_spin_init(&sc->bipbuf_lock, PTHREAD_PROCESS_PRIVATE);
    pthread_mutex_init(&sc->mtx, NULL);
    pthread_cond_init(&sc->cnd, NULL);

    if ((ret = pthread_create(&sc->task, NULL, syscom_handler, sc)) != 0) {
        syscom_log_err("create thread failed");
        return NULL;
    }
    /* waiting for thread startup */
    sleep(1);
#elif defined(OS_DNA)
    sc->hdl_queue = dna_queue_create(NameStr(hdl_queue), sizeof(struct syscom_queue), SYSCOM_QUEUE_DEEP_MAX);
    if (sc->hdl_queue == NULL)
    {
        syscom_log_err("%s create failed", NameStr(hdl_queue));
        return NULL;
    }

    sc->hdl_task = dna_task_create(NameStr(hdl_task), stack_size, \
                        DNA_RTOS_PRIO_1, syscom_main_task, syscom);
    if (sc->hdl_task == NULL)
    {
        syscom_log_err("%s create failed", NameStr(hdl_task));
        return NULL;
    }
#endif

    syscom_log_inf("syscom env %p stack %d bipbuf size %d open success", sc, stack_size, size);
    return (syscom_handle_t)sc;
}

int syscom_close(syscom_handle_t syscom)
{
    struct syscom_env *sc = syscom;

    sc->task_alive = 0;
    SYSCOM_TRG(&sc->cnd, &sc->mtx);
    syscom_log_dbg("trigger cond");

#ifdef OS_LINUX
    pthread_join(sc->task, NULL);
    syscom_log_inf("syscom %p task delete done", sc);

    pthread_cond_destroy(&sc->cnd);
    pthread_mutex_destroy(&sc->mtx);
    pthread_spin_destroy(&sc->bipbuf_lock);
    pthread_spin_destroy(&sc->mplink_mgr_lock);
#elif defined(OS_DNA)

#endif

    MPLINK_LOCK(&mplink_env_lock);
    int ret = mplink_del(&mplink_env_free, &mplink_env_using, sc);
    MPLINK_UNLOCK(&mplink_env_lock);
    if (ret != 0) {
        syscom_log_err("del syscom failed %d", ret);
        return ret;
    }

    return ret;
}

/**
 * @brief
 * @param  None
 * @retval None
 */
int syscom_init(void)
{
#ifdef OS_LINUX
    pthread_spin_init(&mplink_env_lock, PTHREAD_PROCESS_PRIVATE);
#elif defined(OS_DNA)

#endif
    mplink_init(mplink_env, SYSCON_ENV_NUM_MAX, &mplink_env_free, &mplink_env_using, \
                sc_env, sizeof(struct syscom_env));
    syscom_log_inf("syscom init done");
    return 0;
}

/* End of file */
